import torch
import torch.nn as nn
from torch.hub import load_state_dict_from_url


model_urls = {
    'resnet18': 'https://download.pytorch.org/models/resnet18-5c106cde.pth',
    'resnet34': 'https://download.pytorch.org/models/resnet34-333f7ec4.pth',
    'resnet50': 'https://download.pytorch.org/models/resnet50-19c8e357.pth',
    'resnet101': 'https://download.pytorch.org/models/resnet101-5d3b4d8f.pth',
    'resnet152': 'https://download.pytorch.org/models/resnet152-b121ed2d.pth',
    'resnext50_32x4d': 'https://download.pytorch.org/models/resnext50_32x4d-7cdf4587.pth',
    'resnext101_32x8d': 'https://download.pytorch.org/models/resnext101_32x8d-8ba56ff5.pth',
    'wide_resnet50_2': 'https://download.pytorch.org/models/wide_resnet50_2-95faca4d.pth',
    'wide_resnet101_2': 'https://download.pytorch.org/models/wide_resnet101_2-32ee1156.pth',
}


def conv3x3(in_planes,out_planes,stride=1,padding =1):
    return nn.Conv2d(in_planes,out_planes,kernel_size=3,stride=stride,padding=padding,bias=False)

def conv1x1(in_planes,out_planse,stride=1):
    return nn.Conv2d(in_planes,out_planse,kernel_size=1,stride=stride,bias=False)

class BasicBlock(nn.Module):
    def __init__(self,inplanes,planes,stride=1,downsample = None,norm_layer =None):
        super(BasicBlock, self).__init__()
        if norm_layer is None:
            norm_layer = nn.BatchNorm2d
        self.conv1 = conv3x3(inplanes,planes,stride)
        self.bn1 = norm_layer(planes)
        self.relu = nn.ReLU(inplace=True)
        self.conv2 = conv3x3(planes,planes)
        self.bn2 = norm_layer(planes)
        self.downsample = downsample
        self.stride = stride

    def forward(self,x):
        identify = x
        out = self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)

        if self.downsample is not None:
            identify = self.downsample(x)
        out = out+identify
        out = self.relu(out)
        return out


class BottleNeck(nn.Module):
    expansion = 4
    def __init__(self,inplanes,planes,stride =1,downsample =None,norm_layer =None):
        super(BottleNeck, self).__init__()
        if norm_layer is None:
            norm_layer = nn.BatchNorm2d
        self.conv1 = conv1x1(inplanes,planes)
        self.bn1 = norm_layer(planes)
        self.relu = nn.ReLU(inplace=True)

        self.conv2 = conv3x3(planes,planes,stride)
        self.bn2 = norm_layer(planes)
        self.conv3 = conv1x1(planes,planes*self.expansion)
        self.bn3 = norm_layer(planes*self.expansion)

        self.downsample = downsample

    def forward(self,x):
        identify = x

        out =self.conv1(x)
        out = self.bn1(out)
        out = self.relu(out)

        out = self.conv2(out)
        out = self.bn2(out)
        out = self.relu(out)

        out = self.conv3(out)
        out = self.bn3(out)

        if self.downsample is not None:
            identify = self.downsample(x)
        out +=identify
        return out

class Resnet(nn.Module):
    def __init__(self,block,layers,num_class = 1000,norm_layer =None):
        super(Resnet, self).__init__()#调用父类的初始化函数
        if norm_layer is None:
            norm_layer = nn.BatchNorm2d
        self._normlayers = norm_layer
        self.inplanes = 64
        self.conv1 = nn.Conv2d(3,self.inplanes,kernel_size=7,stride=2,padding=3,bias=False)
        self.bn1 = norm_layer(self.inplanes)
        self.relu = nn.ReLU(inplace=True)
        self.maxpool = nn.MaxPool2d(kernel_size=3,stride=2,padding=1)
        self.layer1 = self._make_layer(block,64,layers[0])
        self.layer2 = self._make_layer(block,128,layers[1],stride=2)
        self.layer3 = self._make_layer(block,256,layers[2],stride=2)
        self.layer4 = self._make_layer(block,512,layers[3],stride=2)
        # self.avgpool = nn.AdaptiveAvgPool2d((1,1))
        # self.fc = nn.Linear(512*block.expansion,num_class)

        for m in self.modules():
            if isinstance(m,nn.Conv2d):
                nn.init.kaiming_normal_(m.weight,mode='fan_out',nonlinearity='relu')
            elif isinstance(m,(nn.BatchNorm2d,nn.GroupNorm)):
                nn.init.constant_(m.weight,1)
                nn.init.constant_(m.bias,0)
    def _make_layer(self,block,planes,blocks,stride=1):
        norm_layer =self._normlayers
        downsample =None

        if stride !=1 or self.inplanes !=planes*block.expansion:
            downsample = nn.Sequential(
                conv1x1(self.inplanes,planes*block.expansion,stride),
                norm_layer(planes*block.expansion)
            )
        layers = []
        layers.append(block(self.inplanes,planes,stride,downsample,norm_layer))
        self.inplanes = planes*block.expansion
        for _ in range(1,blocks):
            layers.append(block(self.inplanes,planes,norm_layer = norm_layer))
        return nn.Sequential(*layers)
    def forward(self,x):
        x = self.conv1(x)
        x =self.bn1(x)
        f0 = self.relu(x)
        x = self.maxpool(f0)

        f1 = self.layer1(x)
        f2 = self.layer2(f1)
        f3 = self.layer3(f2)
        f4 = self.layer4(f3)

        # x = self.avgpool(x)
        # x = torch.flatten(x,1)
        # x = self.fc(x)
        return [f0,f1,f2,f3,f4]
def _resnet(arch, block, layers, pretrained, progress, **kwargs):
    model = Resnet(block, layers, **kwargs)
    if pretrained:
        state_dict = load_state_dict_from_url(model_urls[arch],
                                              progress=progress)
        dd = model.state_dict()
        for k in state_dict.keys():
            if k in dd.keys() and not k.startswith('fc'):
                dd[k] = state_dict[k]
        model.load_state_dict(dd)
    return model

def resnet101(pretrained = False,progress=True,**kwargs):
    return _resnet('resnet101',BottleNeck,[3,4,23,3],pretrained,progress,**kwargs)

if __name__ == '__main__':
    model = resnet101(pretrained=True)
    model.eval()
    x = torch.randn(1,3,256,256)
    y = model(x)
    for i in y:
        print(i.shape)

